import { keywordCompare, truthy } from '@gnlab/utils'
import { useBoolean, useRefValue, useAsyncEffect, useStateWithRef } from '@gnlab/utils/env-react'
import { OrderedSet, OrderedMap } from 'immutable'
import { useStreams } from '@/lib/utils'
import services from '@/services'
import type {
  ExternalContact,
  ExternalItemTag,
  BaseExternalSearchConditions,
} from '@/services/contact-types'
import { searchExternalsByTags, batchGetExternalInfo } from '@/services/contact-types/externals/api'
import { splitContactId } from '@/services/contacts'
import { type MomentContact } from '@/services/features/send-moment'
import type { AccountQywx } from '@/services/qywxs'
import { getFormattedQywxs } from '@/services/qywxs'

/**
 * 实体数据缓存
 */
export type Cache = ReturnType<typeof useCache>

export function useCache() {
  const [, setTagMap, tagMapRef] = useStateWithRef(OrderedMap<string, ExternalItemTag>())
  const [, setContactMap, contactMapRef] = useStateWithRef(OrderedMap<string, ExternalContact>())
  const [, setQywxMap, qywxMapRef] = useStateWithRef(OrderedMap<string, AccountQywx>())

  function save<T>(
    getId: (item: T) => string,
    setter: React.Dispatch<React.SetStateAction<OrderedMap<string, T>>>,
    items: T[],
  ) {
    setter(map => {
      const merged = map.merge(items.map(item => [getId(item), item]))
      return merged.size !== map.size ? merged : map
    })
  }

  const cache = useMemo(() => {
    return {
      get tags() {
        return tagMapRef.current
      },
      saveTags(tags: ExternalItemTag[]) {
        save(tag => tag.id, setTagMap, tags)
      },

      get contacts() {
        return contactMapRef.current
      },
      saveContacts(contacts: ExternalContact[]) {
        save(contact => contact.id, setContactMap, contacts)
      },

      get qywxs() {
        return qywxMapRef.current
      },
      saveQywxs(qywxs: AccountQywx[]) {
        save(qywx => qywx.wxid, setQywxMap, qywxs)
      },
    }
  }, [tagMapRef, contactMapRef, qywxMapRef])

  return cache
}

/**
 * 初始化选择数据，排除无效内容
 *
 * 1. 加载企业微信数据，排除 configWxids 中不可用的企业微信，及对应的联系人
 * 2. 加载标签数据，排除联系人选中记录中无效的标签，如果联系人不再有选中记录，则将其也排除
 * 3. 加载联系人数据，排除加载不到信息的联系人
 *
 * 以上步骤结束后，界面显示“选择内容”所需的所有信息，都将可以在 dataMap 中获取到；
 * 后续选中新标签和新联系人，应将它们也保存进 dataMap，保持始终能在 dataMap 里拿到所有数据。
 */
export function useInitialData(
  rawConfigWxids: string[],
  rawInputChoosed: MomentContact[],
  cache: Cache,
) {
  const [configQywxs, setConfigQywxs] = useState<AccountQywx[] | null>([])

  const [inputChoosed, setInputChoosed] = useState<MomentContact[] | null>(null)

  useAsyncEffect(async () => {
    let validChoosed = rawInputChoosed

    // 拉取企业微信资料
    const qywxsRes = await getFormattedQywxs('', false)
    if (qywxsRes.success) cache.saveQywxs(qywxsRes.data)
    // 排除 rawConfigWxids 中拉不到信息的企业微信
    const configQywxs = rawConfigWxids
      .map(wxid => cache.qywxs.get(wxid))
      .filter((qywx): qywx is AccountQywx => !!qywx)
    setConfigQywxs(configQywxs)
    // 排除企业微信无效的联系人
    const wxidSet = new Set(configQywxs.map(qywx => qywx.wxid))
    validChoosed = validChoosed.filter(item => wxidSet.has(item.ownerWxid))

    // 拉取标签资料
    const corpIds = configQywxs.map(qywx => qywx.corpid)
    const tagsRes = await services.externals.tags.getCorpTags(corpIds)
    if (tagsRes.success) cache.saveTags(tagsRes.data)
    // 排除联系人的无效标签，以及排除标签后不再被选中的联系人
    validChoosed = validChoosed
      .map(item => ({
        ...item,
        tagIds: item.tagIds.filter(tagId => tagId === '' || cache.tags.get(tagId)),
      }))
      .filter(item => item.tagIds.length)

    // 拉取联系人资料
    const contactsRes = await batchGetExternalInfo(validChoosed.map(item => ({ id: item.id })))
    if (contactsRes.success)
      cache.saveContacts(
        [...contactsRes.data.values()].filter((item): item is ExternalContact => item !== null),
      )
    // 排除拉取不到资料的联系人
    validChoosed = validChoosed.filter(item => cache.contacts.get(item.id))
    setInputChoosed(validChoosed)
  }, [rawConfigWxids, rawInputChoosed, cache])

  return { configQywxs, inputChoosed }
}

/**
 * 处理“选择”行为
 */

// Map<tagId, Set<contactId>>    按选中的渠道划分（一个联系人可在多个渠道里被选中）；tagId 为 null 对应“手动选择”渠道。
export type Choosed = OrderedMap<string | null, OrderedSet<string>>
const emptyChoosed: Choosed = OrderedMap([[null, OrderedSet()]])

function fromInputChoosed(input: MomentContact[]): Choosed {
  let choosed: Choosed = emptyChoosed
  input.forEach(item => {
    item.tagIds.forEach(rawKey => {
      const key = rawKey === '' ? null : rawKey
      if (!choosed.has(key)) choosed = choosed.set(key, OrderedSet())
      choosed = choosed.set(key, choosed.get(key)!.add(item.id))
    })
  })
  return choosed
}

function toInputChoosed(choosed: Choosed): MomentContact[] {
  const map = new Map<string, MomentContact>()
  for (const [tagId, contactIds] of choosed) {
    for (const contactId of contactIds) {
      if (!map.has(contactId)) {
        const [ownerWxid, contactUserId] = splitContactId(contactId)
        map.set(contactId, { id: contactId, ownerWxid, contactUserId, tagIds: [] })
      }
      map.get(contactId)!.tagIds.push(tagId ?? '')
    }
  }
  return [...map.values()]
}

export function useChoosed(cache: Cache, inputChoosed: MomentContact[] | null) {
  const [choosed, setChoosed] = useState<Choosed | null>(null)
  if (inputChoosed !== null && choosed === null) setChoosed(fromInputChoosed(inputChoosed))

  /**
   * 清空选中数据
   */
  function clear() {
    setChoosed(emptyChoosed)
  }

  /**
   * 选中 / 取消选中
   */
  const choose = useCallback(
    (contacts: ExternalContact[], isChoosed: boolean, tagId?: string) => {
      if (isChoosed) cache.saveContacts(contacts)
      setChoosed(choosed => {
        if (!choosed) return choosed

        const key = tagId ?? null
        const source = choosed.get(key) ?? OrderedSet()
        const method = isChoosed ? 'merge' : 'subtract'
        const ids = contacts.map(v => v.id)
        const updatedSource = source[method](ids)
        return updatedSource.size ? choosed.set(key, updatedSource) : choosed.remove(key)
      })
    },
    [cache],
  )

  return {
    choosed,
    setChoosed,
    choose,
    clear,
    formatChoosed: toInputChoosed,
  }
}

/**
 * 统计“选择”数据
 * manual: 手动选择了多少客户
 * tags: 各标签共选择了多少客户（去重）
 * total: 共选择了多少客户（去重）
 */
export function useChoosedCount(choosed: Choosed | null) {
  const byManual = choosed?.get(null) ?? OrderedSet()
  const byTags = useMemo(
    () =>
      [...(choosed ?? [])].reduce(
        (result, [tagId, source]) => (tagId === null ? result : result.merge(source)),
        OrderedSet<string>(),
      ),
    [choosed],
  )
  const total = useMemo(() => byManual.merge(byTags), [byManual, byTags])

  return {
    byManual: byManual.size,
    byTags: byTags.size,
    total: total.size,
  }
}

// ===========================================================================

export type ChoosingItem = { contact: ExternalContact; qywx: AccountQywx; choosed: boolean }

/**
 * ==================================
 * 维护“手动选择”逻辑
 * ==================================
 */
const srvExternalSearch = services.externals.search

/**
 * 判断界面输入的搜索条件与服务中当前的是否一致，是否要开启新搜索
 */
function conditionsEquals(
  keyword: string,
  tagIds: string[],
  wxids: string[],
  base: BaseExternalSearchConditions,
) {
  if (JSON.stringify(wxids) !== JSON.stringify(base.wxids)) return false
  if (tagIds.length)
    return 'tagIds' in base && JSON.stringify(tagIds) === JSON.stringify(base.tagIds)
  return 'keyword' in base && keyword === base.keyword
}

/**
 * 搜索联系人
 */
export function useManualSearching(configQywxs: AccountQywx[]) {
  const [searching] = useStreams(srvExternalSearch.searching)

  const [keyword, setKeyword] = useState('')
  const [tags, setTags] = useState<ExternalItemTag[]>([])
  const [wxid, setWxid] = useState<string | undefined>(undefined)

  const tagIds = useMemo(() => tags.map(tag => tag.id), [tags])
  const wxidsToSearch = useMemo(
    () => (wxid !== undefined ? [wxid] : configQywxs.map(qywx => qywx.wxid)),
    [wxid, configQywxs],
  )

  const triggerSearch = useCallback(() => {
    if (tagIds.length) srvExternalSearch.byTags(tagIds, wxidsToSearch)
    else srvExternalSearch.byKeyword(keyword, wxidsToSearch)
  }, [keyword, tagIds, wxidsToSearch])

  useEffect(() => {
    if (!keyword.length && !tagIds.length) {
      // 当前未在进行搜索，清除之前的搜索结果
      if (searching) srvExternalSearch.clear()
    } else {
      // 开启了新搜索或搜索条件变化
      // 若是在显示其他类型时开启或更新了搜索，则仅在进入当前类型时才触发接口查询
      if (!searching || !conditionsEquals(keyword, tagIds, wxidsToSearch, searching)) {
        triggerSearch()
      }
    }
  }, [searching, keyword, tagIds, wxidsToSearch, triggerSearch])

  // 离开时清理数据
  useEffect(() => {
    return () => srvExternalSearch.clear()
  }, [])

  const searchedContacts: ExternalContact[] = useMemo(() => {
    const contacts = [...(searching?.items.values() ?? [])]
    return tags.length && keyword
      ? // 通过标签搜索的情况下，后端不会进行关键词过滤，需前端自行过滤
        contacts.filter(contact => keywordCompare(keyword, contact.name))
      : contacts
  }, [searching?.items, tags, keyword])

  const searchLoaderProps = useMemo(() => {
    if (!searching)
      return {
        isItemLoaded() {
          return true
        },
        loadMoreItems() {},
        itemCount: 0,
      }

    const finish = 'finish' in searching && searching.finish
    return {
      isItemLoaded(index: number) {
        return finish || index < searchedContacts.length
      },
      itemCount: !finish ? searchedContacts.length + 1 : searchedContacts.length,
      loadMoreItems: async () => srvExternalSearch.next().then(() => {}),
    }
  }, [searching, searchedContacts])

  return {
    keyword,
    setKeyword,
    tags,
    setTags,
    wxid,
    setWxid,
    searchedContacts,
    searchLoaderProps,
    searching: !!searching?.loading,
  }
}

/**
 * 生成列表数据
 */
export function useManualItems(
  searchedContacts: ExternalContact[],
  choosed: OrderedSet<string>,
  cache: Cache,
) {
  const [searching] = useStreams(srvExternalSearch.searching)

  const choosedItems: ChoosingItem[] = useMemo(
    () =>
      searching
        ? []
        : choosed.toArray().map(contactId => {
            const contact = cache.contacts.get(contactId)!
            const qywx = cache.qywxs.get(contact.ownerWxid)!
            return { contact, qywx, choosed: true }
          }),
    [searching, choosed, cache.contacts, cache.qywxs],
  )

  const searchedItems: ChoosingItem[] = useMemo(() => {
    return searchedContacts.map(contact => ({
      contact,
      qywx: cache.qywxs.get(contact.ownerWxid)!,
      choosed: choosed.has(contact.id),
    }))
  }, [searchedContacts, choosed, cache.qywxs])

  const status = !searching
    ? choosed.size
      ? 'show-choosed'
      : 'no-conditions'
    : !searching.items.size && !searching.loading
    ? 'empty'
    : 'show-searched'

  const showingItems =
    status === 'show-choosed' ? choosedItems : status === 'show-searched' ? searchedItems : []

  return { status, showingItems }
}

/**
 * 维护全选状态
 */
export function useManualChooseAll(
  showingItems: ChoosingItem[],
  choose: (contact: ExternalContact[], isChoosed: boolean) => void,
) {
  // null: 没有可选项 0: 没有选中 1: 部分选中 2: 全部选中
  const showingChoosed = showingItems.filter(item => item.choosed).length

  const allChoosed =
    showingItems.length === 0
      ? null
      : showingChoosed === 0
      ? 0
      : showingChoosed < showingItems.length
      ? 1
      : 2

  function chooseAll() {
    const isChoosed = allChoosed !== 2
    choose(
      showingItems.map(item => item.contact),
      isChoosed,
    )
  }

  return {
    allChoosed,
    chooseAll,
  }
}

/**
 * ==================================
 * 维护“标签选择”逻辑
 * ==================================
 */

/**
 * 维护添加到“可见范围”的标签列表
 */
export function useTags(
  cache: Cache,
  configQywxs: AccountQywx[],
  choosed: Choosed, // 已选中联系人
  setChoosed: (choosed: Choosed) => void,
) {
  const [tags, setTags] = useState(() =>
    [...choosed.keys()]
      .map(id => (id !== null ? cache.tags.get(id) : null))
      .filter((tag): tag is ExternalItemTag => !!tag),
  )

  // 高亮标签
  const [focusTag, setFocusTag] = useState(() => tags[0] ?? null)

  // 批量添加、移除标签
  const [updating, startUpdate, finishUpdate] = useBoolean(false)
  const [tagsInitializing, setTagsInitializing] = useState(false) // 标签是否正在初始化选择其下所有联系人
  async function updated(nextTags?: ExternalItemTag[]) {
    finishUpdate()
    if (!nextTags) return

    const addedTags = nextTags.filter(nextTag => !tags.find(tag => tag.id === nextTag.id))
    const removedTags = tags.filter(tag => !nextTags.find(nextTag => nextTag.id === tag.id))

    // 重新选择 focusTag
    // - 有新添加的标签：选最后一个添加的标签
    // - 没有添加，但当前 focusTag 被删除，选整个标签列表第一个标签
    // - 其他情况：高亮标签不变
    const lastAddedTag = addedTags[addedTags.length - 1]
    if (lastAddedTag) {
      setFocusTag(lastAddedTag)
    } else if (focusTag && !nextTags.find(tag => tag.id === focusTag.id)) {
      setFocusTag(nextTags[0] ?? null)
    }

    let updatedChoosed = choosed

    // 对于移除了的标签，取消选中其联系人
    for (const tag of removedTags) {
      updatedChoosed = updatedChoosed.remove(tag.id)
    }

    // 新添加的标签选中其下所有联系人
    setTagsInitializing(true)
    await Promise.all(
      addedTags.map(async tag => {
        const contactsRes = await searchExternalsByTags(
          configQywxs.map(qywx => qywx.wxid),
          [tag.id],
          1,
          100000,
        )
        const tagChoosed = OrderedSet(
          contactsRes.success ? contactsRes.data.map(contact => contact.id) : [],
        )
        updatedChoosed = updatedChoosed.set(tag.id, tagChoosed)
      }),
    )
    setTagsInitializing(false)

    setChoosed(updatedChoosed)
    setTags(nextTags)
  }

  // 移除标签
  function removeTag(id: string) {
    setTags(tags.filter(tag => tag.id !== id))
    if (focusTag?.id === id) {
      const index = tags.findIndex(v => v.id === id)
      const newFocus = (index === 0 ? tags[1] : tags[index - 1]) ?? null
      setFocusTag(newFocus)
    }
    setChoosed(choosed.remove(id))
  }

  return {
    tags,
    focusTag,
    setFocusTag,
    startUpdate,
    updated,
    updating,
    tagsInitializing,
    removeTag,
  }
}

/**
 * 高亮标签联系人列表
 */
export function useFocusTagContacts(
  cache: Cache,
  configQywxs: AccountQywx[],
  focusTag: ExternalItemTag | null,
  choosed: Choosed,
  setChoosed: (choosed: Choosed) => void,
) {
  // 加载联系人
  const [contacts, setContacts] = useState<null | ExternalContact[]>(null)
  const [loading, setLoading] = useState(false)
  const choosedRef = useRefValue(choosed)
  useEffect(() => {
    let cancelled = false
    if (focusTag) {
      setLoading(true)
      void searchExternalsByTags(
        configQywxs.map(qywx => qywx.wxid),
        [focusTag.id],
        1,
        100000,
      ).then(res => {
        if (cancelled) return // 如果加载过程中切换到了其他标签，取消后续处理

        const contacts = res.success ? res.data : []
        setContacts(contacts)

        // 排除已没有此标签的联系人
        const tagChoosed = choosedRef.current.get(focusTag.id)
        if (tagChoosed) {
          const validated = tagChoosed.filter(contactId => contacts.find(c => c.id === contactId))
          if (validated.size !== tagChoosed.size)
            setChoosed(choosedRef.current.set(focusTag.id, validated))
        }

        setLoading(false)
      })
    } else {
      setContacts(null)
    }
    return () => {
      cancelled = true
      setLoading(false)
    }
  }, [focusTag, configQywxs, choosedRef, setChoosed])

  // 过滤联系人
  const [filterKeyword, setFilterKeyword] = useState('')
  const [filterWxid, setFilterWxid] = useState<string | undefined>(undefined)
  const filteredContacts = useMemo(() => {
    if (!contacts) return null
    return contacts.filter(contact => {
      if (truthy(filterWxid) && contact.ownerWxid !== filterWxid) return false
      if (filterKeyword && !keywordCompare(filterKeyword, contact.name + contact.remark))
        return false
      return true
    })
  }, [contacts, filterKeyword, filterWxid])

  // 整理成列表数据
  const choosingItems: ChoosingItem[] = useMemo(() => {
    return (filteredContacts ?? []).map(contact => {
      return {
        contact,
        qywx: cache.qywxs.get(contact.ownerWxid)!,
        choosed: choosed.get(focusTag?.id ?? '')?.has(contact.id) ?? false,
      }
    })
  }, [filteredContacts, cache.qywxs, choosed, focusTag])

  return {
    loading,
    contacts,
    filterKeyword,
    setFilterKeyword,
    filterWxid,
    setFilterWxid,
    choosingItems,
  }
}

/**
 * 维护标签下联系人的选择状态
 */
export function useChooseTagContacts(
  focusTag: ExternalItemTag | null,
  choosingItems: ChoosingItem[],
  choosed: Choosed,
  setChoosed: (choosed: Choosed) => void,
) {
  const choosedRef = useRefValue(choosed)
  const switchChoose = useCallback(
    (contactId: string) => {
      if (!focusTag) return
      let tagChoosed = choosedRef.current.get(focusTag.id) ?? OrderedSet()
      tagChoosed = tagChoosed.has(contactId)
        ? tagChoosed.delete(contactId)
        : tagChoosed.add(contactId)
      setChoosed(choosedRef.current.set(focusTag.id, tagChoosed))
    },
    [focusTag, choosedRef, setChoosed],
  )

  // null: 没有可选项 0: 没有选中 1: 部分选中 2: 全部选中
  const showingChoosed = choosingItems.filter(item => item.choosed).length
  const allChoosed =
    choosingItems.length === 0
      ? null
      : showingChoosed === 0
      ? 0
      : showingChoosed < choosingItems.length
      ? 1
      : 2
  function chooseAll() {
    if (!focusTag) return
    const newChoosed = choosed.set(
      focusTag.id,
      allChoosed !== 2 ? OrderedSet() : OrderedSet(choosingItems.map(item => item.contact.id)),
    )
    setChoosed(newChoosed)
  }

  return {
    switchChoose,
    allChoosed,
    chooseAll,
  }
}
